package com.wpi.smarttour;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.wpi.smarttour.helpers.LocationListenerHelper;
import com.wpi.smarttour.helpers.SmartTourDBAdapter;
import com.wpi.smarttour.helpers.SmartUploader;
import com.wpi.smarttour.helpers.Utilities;

public class SmartRecording extends Activity
{
   String msg;
   // GPS
   LocationListenerHelper locationListener;
   LocationManager locationManager;
   // Controls the frequency of GPS readings
   static final private int minimumSeconds = 6;
   static final private int minimumDistance = 0;
   // Menu Items
   static final private int MENU_START_GPS = Menu.FIRST;
   static final private int MENU_END_GPS = Menu.FIRST + 1;
   // Recording Options
   private static int RECORD_VIDEO = 1;
   private static int HIGH_VIDEO_QUALITY = 1;
   private static int MMS_VIDEO_QUALITY = 0;
   // GPS counter
   private static int counter = 0;
   // Video File name in database before knowing attual name
   public static String fileNameForReplacing = "smarttour.3gp";
   public static String fileNameForQuery = "'smarttour.3gp'";
   // Is recording started?
   public static boolean isRecording = false;
   // Create Database Variable
   SmartTourDBAdapter DB;
   // Pathfinding, Closest point on the line, Snap To Grid var
   PathFinder finder;
   private static int NEAREST_POINT_RADIUS = 3;
   // Uploader
   SmartUploader uploader;
   // Path of last recorded file
   public static String lastRecordedFilePath = "";

   @Override
   public void onCreate(Bundle savedInstanceState)
   {
      super.onCreate(savedInstanceState);
      // Layout
      setContentView(R.layout.recording);
      Utilities.LogDebug("SmartRecording has Started");
      // Button on Recorder tab
      final Button button = (Button) findViewById(R.id.recorderButton);
      button.setOnClickListener(new View.OnClickListener()
      {
         public void onClick(View v)
         {
            // recordVideo(Uri.parse(PATH_TO_SAVE)); // start video with Path
            recordVideo(null); // start video
         }
      });
      // Database
      DB = new SmartTourDBAdapter(this);
      // Pathfinder/Snap to Grid
      finder = new PathFinder();
      // Uploader
      uploader = new SmartUploader(Utilities.IP_ADDRESS + "/ST/uploader.php");
   }

   // Create Menu
   public boolean onCreateOptionsMenu(Menu menu)
   {
      super.onCreateOptionsMenu(menu);
      // Create and add new menu items
      // Map Functions
      menu.add(0, MENU_START_GPS, Menu.NONE, "Start GPS");
      menu.add(0, MENU_END_GPS, Menu.NONE, "End GPS");
      return true;
   }

   // Menu Click
   public boolean onOptionsItemSelected(MenuItem item)
   {
      super.onOptionsItemSelected(item);
      TextView GPSText = (TextView) findViewById(R.id.gpsText);
      switch(item.getItemId())
      {
         case (MENU_START_GPS):
         {
            // GPS
            GPSText.setText("GPS: On");
            startGPS();
            return true;
         }
         case (MENU_END_GPS):
         {
            // End GPS
            Utilities.LogDebug("MapActivity: Select View Selected");
            GPSText.setText("GPS: OFF");
            stopLocationMonitoring();
            return true;
         }
      }
      return false;
   }

   // Uses the core recorder on Android to record video
   public void recordVideo(Uri outputpath)
   {
      // Intent for Recording
      Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
      if (outputpath != null)
         intent.putExtra(MediaStore.EXTRA_OUTPUT, outputpath);
      intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, HIGH_VIDEO_QUALITY);
      // Tell the database that recording has started
      isRecording = true;
      startActivityForResult(intent, RECORD_VIDEO);
   }

   // Gets called after returning from Recording
   @Override
   protected void onActivityResult(int requestCode, int resultCode, Intent data)
   {
      if (requestCode == RECORD_VIDEO)
      {
         Utilities.LogDebug("SR-onActivityResult", "Returned from video recording");
         // Tell database recording has stoped
         isRecording = false;
         Uri recordedVideo = data.getData(); // video file
         TextView tv = (TextView) findViewById(R.id.myLocationText);
         // Path + Recorded File Name
         String[] results = getRealPathFromURI(recordedVideo);
         String videoPath = results[0];
         lastRecordedFilePath = videoPath;
         String videoName = videoPath.substring(videoPath.lastIndexOf('/') + 1);
         String output = videoPath + "\n"
            + videoName + "\n"
            + "Video Time Taken: " + results[1];
         tv.setText(output);
         // For now we are setting the database file name to a 'dummy' file name
         // To change that file name to a real one, call this function
         // This function also uses snap to grid
         // So all the GPS coords that were just recorded will be snapped
         // to the closest X and Y
         updateLastRecordedFile(videoName);
         // Make a new entry to the SEPostion Table which is used for the 2nd
         // milestone
         // Also used to find out the starting time of a video
         addToSEPositionsTable(videoName);
         // Add to nodes to the VideoNodes Tables
         addVideoNodes(videoName);
      }
   }

   /*
    * Contains code to start the GPS listeners This uses the helper class
    * LocationListenerHelper This starts the listener and sets this class also
    * sets the min time and distance to receive updates
    */
   private void startGPS()
   {
      // Location
      String context = Context.LOCATION_SERVICE;
      locationManager = (LocationManager) getSystemService(context);
      Utilities.msgUserShort("Recording:GPS is starting", getApplicationContext());
      // Criteria for what Location thing to use
      // Android picks the best choice
      Criteria criteria = new Criteria();
      criteria.setAccuracy(Criteria.ACCURACY_FINE);
      criteria.setAltitudeRequired(false);
      criteria.setBearingRequired(false);
      criteria.setCostAllowed(false);
      criteria.setPowerRequirement(Criteria.POWER_HIGH);
      String provider = locationManager.getBestProvider(criteria, true);
      // Location location =
      // locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
      Location location = locationManager.getLastKnownLocation(provider);
      if (location != null)
         displayNewLocation(location);
      // Set the location listener
      // time is in milliseconds
      // distance is in meters
      locationListener = new LocationListenerHelper(this);
      locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minimumSeconds * 1000,
            minimumDistance, locationListener);
      locationManager.addGpsStatusListener(locationListener);
   }

   /*
    * Gets called from the GPS Listener Only gets called if recording is
    * happening This is so it doesn't lag by outing information in Other
    * activity.
    */
   public void updateWithNewLocation(Location location)
   {
      Utilities.LogDebug("SR-updateWithNewLocation", "New Location");
      if (location != null)
      {
         writeToSQL(location);
      }
   }

   /*
    * Gets called if recording is NOT happening This shows location information
    * and accuracy of the GPS
    */
   public void displayNewLocation(Location location)
   {
      String latLongString;
      TextView myLocationText;
      myLocationText = (TextView) findViewById(R.id.myLocationText);
      Utilities.LogDebug("SmartRecording: in displayNewLocation");
      if (location != null) // don't run if location doesn't exist
      {
         counter++;
         Utilities.LogDebug("SmartRecording: in displayNewLocation() - Location is NOT null");
         writeToSQL(location); // database call
         double lat = location.getLatitude();
         double lng = location.getLongitude();
         latLongString = "Lat:" + lat + "\nLong:" + lng;
         // Check if the location received came from GPS or cell tower
         String providerName = location.getProvider();
         if (providerName.equalsIgnoreCase("gps"))
         {
            providerName = "GPS";
         }
         else
         {
            providerName = "CELLTOWER";
         }
         myLocationText.setText("Your Current Position is:\n" +
               latLongString + "\n" + "\n" +
               "\n" + "counter: " + counter +
               "\n" + "Recording Active: " + isRecording +
               "\n" + "providerName: " + providerName +
               "\n" + "sat fix: " + location.getExtras() +
               "\n" + "Accuracy in meters: " + location.getAccuracy());
      }
      else
      {
         Utilities.msgUserShort("Location is NULL", getApplicationContext());
         myLocationText.setText("Location Not Found");
      }
   }

   /*
    * Stops the GPS Listeners
    */
   public void stopLocationMonitoring()
   {
      Utilities.LogInfo("Stopping GPS managers");
      if (locationListener != null)
      {
         locationManager.removeUpdates(locationListener);
         locationManager.removeGpsStatusListener(locationListener);
      }
      Utilities.msgUser("GPS Manager Stoped", getApplicationContext());
   }

   /*
    * Saves GPS information to a database table The file is saved as a set name
    * which gets changed after the recording is finished This function uses the
    * helper SmartTourDBAdapter which talks to the SQLite database file
    */
   private void writeToSQL(Location _location)
   {
      DB.open();
      String _fileName = fileNameForReplacing;
      long _time = _location.getTime(); // get GPS time
      Utilities.LogDebug("SR-writeToSQL: Saving Location to Database");
      long checkForError = DB.insertLocation(_location, _fileName, isRecording, _time);
      if (checkForError == -1)
      {
         Utilities.LogError("SR-writeToSQL: Error with inserting");
         Utilities.msgUserShort("SR: Error with Saving", getApplicationContext());
      }
      else
      {
         Utilities.LogDebug("SR-writeToSQL: Location Saved");
         // Utilities.msgUserShort("SR: Saved", getApplicationContext());
      }
      DB.close(); // close DB so there is no memory leak
   }

   /*
    * This function is used to query the MediaStore class which returns filepath
    * and time using the URI
    * 
    * Input: URI Output: String[] File Path, File Starting Time
    */
   public String[] getRealPathFromURI(Uri contentUri)
   {
      String[] results = new String[2];
      try
      {
         String[] proj = {MediaStore.Images.Media.DATA, MediaStore.Video.Media.DATE_TAKEN};
         Cursor cursorPath = managedQuery(contentUri, proj, null, null, null);
         int column_index = cursorPath.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
         int column_index_2 = cursorPath.getColumnIndexOrThrow(MediaStore.Video.Media.DATE_TAKEN);
         cursorPath.moveToFirst();
         results[0] = cursorPath.getString(column_index);
         cursorPath.close();
         results[1] = cursorPath.getString(column_index_2);
      }
      catch (Exception e)
      {
         e.printStackTrace();
      }
      return results;
   }

   /*
    * This function is called to update/fix the database after the file is
    * finished recording
    * 
    * Input: The atual file name of the recorded file Output: NONE (Fixes
    * database)
    */
   public void updateLastRecordedFile(String _newFileName)
   {
      DB.open();
      Utilities.LogDebug("SR-replaceFileName", "Starting to replace file names with new Names");
      // Database Query
      Cursor cursor = DB.getAllReplaceableFileName(fileNameForQuery);
      Utilities.LogDebug("SR-replaceFileName", "Query returned without error");
      Utilities.LogDebug("SR-replaceFileName", "Counter: " + Integer.toString(cursor.getCount()));
      if (cursor.moveToFirst()) // Make sure there is a at least one row
      {
         do // Loop through all the rows that were returned
         {
            int _id = cursor.getInt(cursor.getColumnIndex(SmartTourDBAdapter.KEY_ID));
            Utilities.LogDebug("SR-replaceFileName", "Replacing Id::" + _id);
            DB.updateFileName(_id, _newFileName);
            /*
             * This block of code will do the following Change GPS coords to X
             * and Y that relate to the map img Then it will send it to the
             * snapping code Then it will put these new X and Y to the database
             */
            int[] snapedCoords = MapActivity.GPSToXY(cursor.getDouble(
                  cursor.getColumnIndex(SmartTourDBAdapter.KEY_LAT)),
                  cursor.getDouble(cursor.getColumnIndex(SmartTourDBAdapter.KEY_LONG)));
            Utilities.LogDebug("SR-replaceFileName", "Orginal X and Y::" + snapedCoords[0] + "-" + snapedCoords[1]);
            snapedCoords = finder.getClosestSegment(snapedCoords[0], snapedCoords[1]);
            Utilities.LogDebug("SR-replaceFileName", "Snapped X and Y::" + snapedCoords[0] + "-" + snapedCoords[1]);
            DB.updateXY(_id, snapedCoords[0], snapedCoords[1]);
         } while(cursor.moveToNext()); // Move the pointer to the next row
         Utilities.LogDebug("SR-replaceFileName", "DONE");
      }
      // Close
      cursor.close();
      DB.close();
   }

   /*
    * Add Information about the video file to database table Used to get the
    * start of a video file Input: File Name which is used for the DB query
    */
   public void addToSEPositionsTable(String _fileName)
   {
      DB.open();
      // When Querying the server you need quotes around this file name because
      // of the period
      _fileName = "'" + _fileName + "'";
      Utilities.LogDebug("SR-addToSEPositionsTable", "Attempting to add new row to sepostions");
      Cursor cursor = DB.getAllLocationItemsPerVideo(_fileName);
      Utilities.LogDebug("SR-addToSEPositionsTable", "Query returned without error");
      Utilities.LogDebug("SR-addToSEPositionsTable", "Counter: " + Integer.toString(cursor.getCount()));
      if ((cursor.getCount()) != 0)
      {
         // First Entry
         cursor.moveToFirst();
         int[] startingXY = new int[2];
         startingXY = MapActivity.GPSToXY(cursor.getDouble(cursor.getColumnIndex(SmartTourDBAdapter.KEY_LAT)),
               cursor.getDouble(cursor.getColumnIndex(SmartTourDBAdapter.KEY_LONG)));
         long startingTime = cursor.getLong(cursor.getColumnIndex(SmartTourDBAdapter.KEY_TIME));
         cursor.moveToLast();
         // Last Entry
         int[] endingXY = new int[2];
         endingXY = MapActivity.GPSToXY(cursor.getDouble(cursor.getColumnIndex(SmartTourDBAdapter.KEY_LAT)),
               cursor.getDouble(cursor.getColumnIndex(SmartTourDBAdapter.KEY_LONG)));
         long endingTime = cursor.getLong(cursor.getColumnIndex(SmartTourDBAdapter.KEY_TIME));
         // Figure this out later
         // use Location.distanceBetween(double startLatitude, double
         // startLongitude,
         // double endLatitude, double endLongitude, float[] results)
         double distance = 0;
         DB.insertXYPerVideo(_fileName, startingXY[0], startingXY[1],
               endingXY[0], endingXY[1], startingTime, endingTime, distance);
         Utilities.LogDebug("SR-addToSEPositionsTable", "ADDED");
      }
      else
         Utilities.LogDebug("SR-addToSEPositionsTable", "The file does not have any GPS tags, re-record");
      DB.close();
   }

   /*
    * Adds nodes to database These nodes correlate to video and pathfinding
    * nodes This uses the pathfinder class to find nodes
    */
   public void addVideoNodes(String _fileName)
   {
      DB.open();
      // int lastNodeCounter = 0;
      Utilities.LogDebug("SR-addVideoNodes", "Starting");
      _fileName = "'" + _fileName + "'"; // This is needed when you query the
                                         // server
      // Get Snapped X and Y coords from a DB table
      Cursor cursor = DB.getAllXYFromFileName(_fileName);
      Utilities.LogDebug("SR-addVideoNodes", "Query returned without error");
      int dbCount = cursor.getCount();
      Utilities.LogDebug("SR-addVideoNodes", "Counter: " + dbCount);
      // Add nodes to database by finding out what is the closest node
      // also checks to see if that is the closest point to the node
      if (cursor.moveToFirst())
      {
         int lastNode = -1, lastDistance = -1;
         long lastTime = -1, bufferTime = -1;
         int bufferNode = -1, bufferDistance = -1;
         do
         {
            dbCount--; // counter
            Utilities.LogDebug("SR-addVideoNodes", "Count: " + dbCount);
            int _id = cursor.getInt(cursor.getColumnIndex(SmartTourDBAdapter.KEY_ID));
            Utilities.LogDebug("SR-addVideoNodes", "Id::" + _id);
            int x = cursor.getInt(cursor.getColumnIndex(SmartTourDBAdapter.KEY_X));
            int y = cursor.getInt(cursor.getColumnIndex(SmartTourDBAdapter.KEY_Y));
            long time = cursor.getLong(cursor.getColumnIndex(SmartTourDBAdapter.KEY_TIME));
            int[] nearestNode = finder.nearestNode(x, y);
            Utilities.LogDebug("---------------------------------------------------");
            Utilities.LogDebug("SR-addVideoNodes", "LAST NODE ::" + lastNode);
            Utilities.LogDebug("SR-addVideoNodes", "NEAREST NODE ::" + nearestNode[1]);
            Utilities.LogDebug("SR-addVideoNodes", "DISTANCE NODE ::" + nearestNode[0]);
            Utilities.LogDebug("---------------------------------------------------");
            if (lastNode == -1) // first XY
            {
               lastDistance = nearestNode[0];
               lastNode = nearestNode[1];
               lastTime = time;
               Utilities.LogDebug("SR-addVideoNodes", "Last Node = -1 -- Setting LastNode to current Node");
            }
            else if (dbCount == 0) // Last XY
            {
               if (lastNode != nearestNode[1])
               {
                  Utilities.LogDebug("SR-addVideoNodes", "Adding to DB ---Last DB Entry");
                  DB.insertVideoNode(_fileName, lastNode, nearestNode[1], lastTime, time);
               }
               else
               {
                  Utilities.LogDebug("SR-addVideoNodes", "Last DB Entry, Nodes are same NOT entering in DB");
               }
            }
            // Change of node
            else if (lastNode != nearestNode[1])
            {
               // third node has come in, save last and buffer
               if ((bufferNode != nearestNode[1]) && (bufferNode != -1) && (lastNode != bufferNode))
               {
                  Utilities.LogDebug("SR-addVideoNodes", "Adding to DB -- buffer");
                  DB.insertVideoNode(_fileName, lastNode, bufferNode, bufferTime, time);
                  lastNode = bufferNode;
                  lastDistance = bufferDistance;
                  lastTime = bufferTime;
                  bufferNode = -1;
                  bufferTime = -1;
                  bufferDistance = -1;
                  Utilities.LogDebug("SR-addVideoNodes", "Last Node changed to current Node");
                  // This is the radius of saving a node
               }
               else if ((nearestNode[0] < NEAREST_POINT_RADIUS) && (nearestNode[1] != lastNode))
               {
                  Utilities.LogDebug("SR-addVideoNodes", "Adding to DB -- NearestPoint");
                  DB.insertVideoNode(_fileName, lastNode, nearestNode[1], lastTime, time);
                  lastNode = nearestNode[1];
                  lastDistance = nearestNode[0];
                  lastTime = time;
                  Utilities.LogDebug("SR-addVideoNodes", "Last Node changed to current Node");
               }
               else
               { // set buffer till we get a closer node
                  Utilities.LogDebug("SR-addVideoNodes", "Buffering");
                  bufferNode = nearestNode[1];
                  bufferTime = time;
                  bufferDistance = nearestNode[0];
               }
            }
            // Same node
            else if (lastNode == nearestNode[1])
            {
               Utilities.LogDebug("SR-addVideoNodes", "Current Node is same as Last node");
               if (nearestNode[0] <= lastDistance) // check if this is closer
                                                   // then last value
               {
                  lastNode = nearestNode[1];
                  lastDistance = nearestNode[0];
                  lastTime = time;
                  Utilities.LogDebug("SR-addVideoNodes", "New Closer Node");
               } // else wait
            }
         } while(cursor.moveToNext());
         Utilities.LogDebug("SR-addVideoNodes", "DONE");
      }
      cursor.close();
      DB.close();
   }
}